Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

roc_audio: adjust resampler coef #113

Merged
merged 1 commit into from
Oct 22, 2017
Merged

roc_audio: adjust resampler coef #113

merged 1 commit into from
Oct 22, 2017

Conversation

dshil
Copy link
Member

@dshil dshil commented Oct 15, 2017

#85

@@ -105,6 +105,9 @@ typedef struct roc_receiver_config {

//! A bitmask of ROC_FLAG_* constants.
unsigned int flags;

//! Number of samples for all channels per second.
unsigned int sample_rate;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This comment looks ambiguous. I suggest "Number of samples per second per channel". We should also fix corresponding comments in ReceiverConfig and SenderConfig.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed

if (rate_limiter_.allow()) {
roc_log(LogDebug, "resampler updater: queue_size=%lu fe=%.5f",
(unsigned long)queue_size, (double)fe_.freq_coeff());
(unsigned long)queue_size, (double)adjusted_coef);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It would be reasonable to print both freq_coeff and adjusted_coef here.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed

@@ -35,7 +35,8 @@ class ResamplerUpdater : public packet::IWriter,
//! - @p update_interval defines how often to call FreqEstimator, in samples
//! - @p aim_queue_size defines FreqEstimator target queue size, in samples
ResamplerUpdater(packet::timestamp_t update_interval,
packet::timestamp_t aim_queue_size);
packet::timestamp_t aim_queue_size,
const float sample_rate_coef);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

sample_rate_coef parameter should be documented.

Copy link
Member Author

@dshil dshil Oct 18, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed

@@ -135,7 +135,7 @@ struct ReceiverConfig {
bool timing;

ReceiverConfig()
: sample_rate(DefaultSampleRate)
: sample_rate(0)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why this change? The idea of default constructors of these *Config structures is that if a field was not overwritten explicitly, it has a reasonable value. This feature will be used in roc_lib, which will overwrite only those fields that were set to non-zero by user.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Providing zero sample rate for sox means, that it'll determine sample rate of the output file or device by its own.

UPD: Later I've checked, that if we provide unsupported sample rate to sox (e.g. 44K, while device supports 48K), sox will also detect the correct sample rate by its own.

I'll rollback this change.

Copy link
Member Author

@dshil dshil Oct 20, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This feature will be used in roc_lib, which will overwrite only those fields that were set to non-zero by user.

Where do we check that the user provides non-zero values?

bool make_receiver_config(pipeline::ReceiverConfig& out, const roc_receiver_config* in) {
    out.default_session.latency = in->latency;
    out.default_session.timeout = in->timeout;
    out.default_session.samples_per_packet = in->samples_per_packet;
    out.sample_rate = in->sample_rate;
   
    //...
}

We'll panic only during a session creation for zero sample rate.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Later I've checked, that if we provide unsupported sample rate to sox (e.g. 44K, while device supports 48K), sox will also detect the correct sample rate by its own.

I think we should not rely on this, it's too implicit and can be changed when switching from SoX to something else.

I suggest the following:

  • Use a reasonable default in ReceiverConfig::sample_rate (44100). This default will be ignored in roc_recv, but it will be used in roc_lib.

  • Use a separate variable in roc_recv, initialize it with --rate option value or zero if it wan't given, pass it to SoX, then get the actual sample rate from SoX, and write it to ReceiverConfig.

Where do we check that the user provides non-zero values?

There are no checks yet. However, I plan to add them in 0.1. It will look like:

if (in->sample_rate) {
    out.sample_rate = in->sample_rate;
}

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed

@@ -35,7 +36,8 @@ ReceiverSession::ReceiverSession(const SessionConfig& config,

if (config.resampling) {
resampler_updater_.reset(new (allocator_) audio::ResamplerUpdater(
config.fe_update_interval, config.latency),
config.fe_update_interval, config.latency,
(float)format->sample_rate / out_sample_rate),
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should panic if out_sample_rate is zero. A panic is better than a segfault.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed

return true;
}

void Player::stop() {
stop_ = 1;
}

void Player::start(pipeline::IReceiver* input) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why a pointer? It's not allowed to be null, so a reference is preferred.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed

if (output_) {
return size_t(output_->signal.rate);
}
return 0;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If output_ is null, it means that open() was not called or failed, and the user called get_sample_rate() anyway. It looks like a bug in the user code, so I think it's better to panic here instead of silently returning zero.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed

@@ -115,7 +128,7 @@ void Player::loop_() {
SOX_SAMPLE_LOCALS;

while (!stop_) {
pipeline::IReceiver::Status status = input_.read(frame);
pipeline::IReceiver::Status status = input_->read(frame);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should panic if run() was called and input_ is null. A panic is better than a segfault.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed

//! Stop thread.
//! @remarks
//! Can be called from any thread.
void stop();

//! Get sample rate
//!
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's worth to mention how this sample rate is obtained.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also, we need a test for this feature.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed

return 1;
}

config.sample_rate = player.get_sample_rate();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we should check if sample_rate is zero and exit with error in this case.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed

@dshil
Copy link
Member Author

dshil commented Oct 20, 2017

ping @gavv

@@ -96,13 +98,16 @@ bool ResamplerUpdater::update(packet::timestamp_t time) {
update_time_ += update_interval_;
}

const float adjusted_coef = float(sample_rate_coef_ * fe_.freq_coeff());
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why do we need a cast?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

o my gosh, at the beginning I had a crazy idea, that we can overflow here, but after your comment, I've reread the code. Both values are near about 1, we should throw this cast.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't get it. If both arguments are floats, this cast simply does nothing.

void Player::run() {
roc_log(LogDebug, "player: starting thread");

if (!input_) {
roc_panic("player: thread is started before device or output file was opened");
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The panic message is a bit misleading, because input_ is set in start(), not in open().

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fixed

@@ -60,11 +58,23 @@ class Player : public core::Thread {
//! Should be called once before calling start().
bool open(const char* name, const char* type = NULL);

//! Start reading samples.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's worth to mention that it starts a thread.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed

//! Get a user defined or auto-detected sample rate of an output file or a device.
//!
//! @remarks
//! Output file or device should be opened.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Two little notices:

  • We can change @remarks to @pre (preconditions) here, it would fit a bit better.

  • We usually indent @remarks, @pre, and similar sections, e.g.:

    //! @pre
    //!  Output file or device should be opened.
    

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed

@@ -223,6 +224,7 @@ TEST_GROUP(sender_receiver) {
receiver_conf.n_repair_packets = n_repair_packets;
receiver_conf.latency = packet_len * 20;
receiver_conf.timeout = packet_len * 300;
receiver_conf.sample_rate = pipeline::DefaultSampleRate;
Copy link
Member

@gavv gavv Oct 20, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since we have the default back, this line can be removed.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed

player.join();
}

TEST(player_recorder, player_get_sample_rate) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we should test three cases:

  • The user provided zero sample rate, SoX selected non-zero sample rate, Player was successfully opened.

  • The user provided non-zero sample rate, SoX selected the same sample rate, Player was successfully opened.

  • The user provided non-zero sample rate, but SoX selected some other sample rate. I think Player::open should fail in this case, because it's generally not a good idea to ignore what the user have asked explicitly.

I'm not sure if it's possible to add a test for the latter case because our tests operate with files instead of devices. However, we need to fix the behaviour anyway.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed

@@ -148,6 +148,22 @@ int main(int argc, char** argv) {
core::BufferPool<audio::sample_t> sample_buffer_pool(allocator, MaxFrameSize, 1);
packet::PacketPool packet_pool(allocator, 1);

sndio::Player player(sample_buffer_pool, allocator, args.oneshot_flag,
config.channels, config.sample_rate);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@@ -34,8 +34,11 @@ class ResamplerUpdater : public packet::IWriter,
//! @b Parameters
//! - @p update_interval defines how often to call FreqEstimator, in samples
//! - @p aim_queue_size defines FreqEstimator target queue size, in samples
//! - @p sample_rate_coef represents an adjustment coefficient for a resampler
//! scaling factor, calculating from sample rates of input and output devices.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

from sample rates of input and output devices

Well, strictly speaking it's not rates of the input and output devices. It's a faсtor to convert session rate to receiver output rate.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed

size_t out_rate = size_t(output_->signal.rate);

if (in_rate != 0 && in_rate != out_rate) {
roc_log(LogError, "player: sample rates mismatch, auto-detected=%zu, input=%zu",
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think the error message should be something like:

"can't open output file or device with the required sample rate: required=%lu suggested=%lu".

Also some notes:

  • we don't use %z, it's not portable
  • we don't use commas when logging multiple values, they don't increase readability, but they make grepping a bit harder

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed

@@ -22,6 +22,7 @@
#include "roc_packet/address_to_str.h"
#include "roc_packet/packet_pool.h"
#include "roc_packet/parse_address.h"
#include "roc_pipeline/config.h"
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This line can be removed I guess.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed


TEST(player_recorder, player_open_sample_rates_mistmatch) {
Player player(buffer_pool, allocator, true, ChMask, SampleRate + 1);
CHECK(!player.open(NULL, NULL));
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There are three problems with this test:

  • Opening an audio device may have side-effects. For example, if the user is using ALSA without dmix, only one process can open an audio device. So this test will either fail to open it (if it's already opened by someone else), or prevent other processes from opening it for a short time.

  • If this test will fail to open device for some other reason (e.g. an ALSA device is already opened by someone else), it will pass anyway, so we really don't known if it actually tests something.

  • I'm not sure that any SoX backend is guaranteed to fail with rate 44100+1.

We theoretically may want a separate set of tests that work with a real audio device. Such tests should not be run automatically, but instead only when the user explicitly asked that. However I'm not sure it's worth it.

Thus, if we can't cover this case with a test that uses a temporary file instead of real audio device, I suggest to remove it for now.

Copy link
Member Author

@dshil dshil Oct 21, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think that you've reviewed an old version of tests, I've rebased recently, but I totally agree with 3 points.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The first two points are still relevant after rebase.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed

@gavv gavv merged commit bb2ffcd into roc-streaming:develop Oct 22, 2017
@dshil dshil deleted the dshil/85-sample-rate branch January 20, 2018 06:55
@gavv gavv modified the milestones: 0.1.1, 0.1.0 Jun 23, 2019
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants